package cn.virens.web.components.mybatis;

import java.sql.Connection;

import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;

import cn.virens.exception.APIException;

/**
 * SqlSession的创建工厂
 * 
 * @author virens
 */
public class VirSqlSessionFactory implements SqlSessionFactory {
	private final VirSqlSessionFactoryBuilder sessionFactory;
	private final Configuration configuration;

	public VirSqlSessionFactory(Configuration configuration, VirSqlSessionFactoryBuilder sessionFactory) {
		this.sessionFactory = sessionFactory;
		this.configuration = configuration;
	}

	@Override
	public SqlSession openSession() {
		return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
	}

	@Override
	public SqlSession openSession(boolean autoCommit) {
		return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
	}

	@Override
	public SqlSession openSession(ExecutorType execType) {
		return openSessionFromDataSource(execType, null, false);
	}

	@Override
	public SqlSession openSession(TransactionIsolationLevel level) {
		return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
	}

	@Override
	public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
		return openSessionFromDataSource(execType, level, false);
	}

	@Override
	public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
		return openSessionFromDataSource(execType, null, autoCommit);
	}

	@Override
	public SqlSession openSession(Connection connection) {
		return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
	}

	@Override
	public SqlSession openSession(ExecutorType execType, Connection connection) {
		return openSessionFromConnection(execType, connection);
	}

	@Override
	public Configuration getConfiguration() {
		return configuration;
	}

	private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
		VirExecutor readExecutor = null;
		VirExecutor writeExecutor = null;

		try {
			final Environment environment = configuration.getEnvironment();
			final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

			readExecutor = new VirExecutor(transactionFactory, configuration, sessionFactory.getReadDataSource(), level, autoCommit, execType);
			writeExecutor = new VirExecutor(transactionFactory, configuration, sessionFactory.getWriteDataSource(), level, autoCommit, execType);

			return new VirSqlSession(configuration, readExecutor, writeExecutor, autoCommit);
		} catch (Exception e) {
			writeExecutor.closeTransaction();
			readExecutor.closeTransaction();

			throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
		} finally {
			ErrorContext.instance().reset();
		}
	}

	private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
		throw new APIException("ERROR", "读写分离，无法判断类型，不支持根据连接获取");
	}

	private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
		if (environment != null && environment.getTransactionFactory() != null) {
			return environment.getTransactionFactory();
		} else {
			return new ManagedTransactionFactory();
		}
	}
}
